import java.util.ArrayList;

import static java.lang.System.out;

public final class HHIterABMiniMaxDrawFix extends IterDeepDrawFix {

    final static Integer ZERO = Integer.valueOf(0);
    final static Integer ONE = Integer.valueOf(1);

    private int[][] HH;
    private int[] fromTo = new int[2];
    private boolean firstRun;


    public HHIterABMiniMaxDrawFix(boolean isBlack, Eval evaluator, int maxDepth)
    {
        super(isBlack, evaluator, maxDepth);
        HH = new int[32][32];
        for (int i = 0; i < 32; i++) {
            for (int j = 0; i < 32; i++) {
                HH[i][j] = 0;
            }
        }
        firstRun = true;
    }


    @Override
    public Board findMove(Board board, ArrayList<Board> moves, int depth) {
    //public Board findMove(Board board, int depth) {
        // IMPORTANT! If the player using findMove is White, then
        // the search method will be called with BLACK to move and
        // vice versa, i.e. THE RETURN VALUE FROM THE SEARCH-CALL WILL
        // REFLECT THE OPPONENTS BEST POSSIBLE SCORE
        assert board.blackToMove == isBlack;

        int bestValue = MIN;
        if (isBlack) {
            bestValue = MAX;
        }
        int alpha = MIN;
        int beta = MAX;


        //ArrayList<Board> moves = board.generateMoves();


        // If no moves found, return null as error flag
        if( moves.isEmpty() ) {
            return null;
        }

        // If only one possible move found - just return that.
        if( moves.size() == 1 ) {
            return moves.get(0);
        }


        // Sort the move-list acc. to HH, unless this is the first run
        if( !firstRun ) {
            Board thisMove = null;
            for (int i = 1; i < moves.size(); i++) {

                thisMove = moves.get(i);

                fromTo(board, thisMove, fromTo);
                int HHval1 = HH[ fromTo[0] ][ fromTo[1] ];

                fromTo(board, moves.get(i-1), fromTo);
                int HHval2 = HH[ fromTo[0] ][ fromTo[1] ];

                for(; i > 0 && HHval2 < HHval1; i-- ) {

                    moves.set(i, moves.get(i-1));
                    fromTo(board, moves.get(i-1), fromTo);
                    HHval2 = HH[ fromTo[0] ][ fromTo[1] ];

                }
                moves.set(i, thisMove);

            }
        } else {
            firstRun = false;
        }


        // Search through move-list for the best move
        Board bestMove = moves.get(0);
        for (Board move : moves) {
            int value = search(move, 0, depth, alpha, beta);
            if (isBlack) {
                if (value < bestValue) {
                    if (value < beta) {
                        beta = value;
                    }
                    bestValue = value;
                    bestMove = move;
                }
            } else {
                if (value > bestValue) {
                    if (value > alpha) {
                        alpha = value;
                    }
                    bestValue = value;
                    bestMove = move;
                }
            }
        }


        if(printBestMoveValue) {
            out.println("The best move has value " + bestValue);
        }

        return bestMove;
    }






    //    @Override
    public int search(Board board, int depth, int maxDepth, int alpha, int beta) {

        int depthLeft = maxDepth - depth;

        ArrayList<Board> moves = board.generateMoves();
        //if unable to move, this is a losing board for current player
        //punish for depth left (embarrassing to lose with much depth left)
        if(moves.isEmpty()) {
            if (board.blackToMove) {
                return BLACK_LOSS+depthLeft;
            } else {
                return WHITE_LOSS-depthLeft;
            }
        }

	for(int i = 0; i < drawBoardsSize; i++) {
	    if ( board.isSame(drawBoards[i]) && isBlack) {
		return DRAW;
	    } else if ( board.isSame(drawBoards[i]) && !isBlack ) { 
		return -DRAW;
	    }
	}


        if(depthLeft == 0) {
            return evaluator.eval(board);
        }

        depth++;
        int nodeCount = 0;
	Board bestMove = moves.get(0);
        while (  (nodeCount < moves.size())  &&  (alpha < beta)  ) {
            Board move = moves.get(nodeCount);
            int value = search(move, depth, maxDepth, alpha, beta);

            if (board.blackToMove) {
                if (value < beta) {
                    beta = value;
                    bestMove = move;
                }
            } else {
                if (value > alpha) {
                    alpha = value;
                    bestMove = move;
                }
            }
            nodeCount++;
        }

        // update HH
        fromTo(board, bestMove, fromTo);
        HH[ fromTo[0] ][ fromTo[1] ] += ( 1 << depth );


        int bestValue;
        if (board.blackToMove) {
            bestValue = beta;
        } else {
            bestValue = alpha;
        }


        return bestValue;
    }
}
